﻿using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics.Contracts;
using System.Linq;
using System.Text;
using Microsoft.Practices.Unity;

namespace Derbster.Domain
{
    public class Jam
    {
        private CommandManager _commandManager;

        [ContractVerification(false)]
        internal Jam()
        {

        }

        public Jam(Match match, int period, int number, int rank)
        {
            Contract.Requires(match != null);
            Contract.Requires(period == 1 || period == 2);
            Contract.Requires(number > 0);
            Contract.Ensures(FirstTeamLineup != null);
            Contract.Ensures(SecondTeamLineup != null);
            Contract.Ensures(FirstTeamScoring != null);
            Contract.Ensures(SecondTeamScoring != null);
            Contract.Ensures(FirstTeamScoring.Passes != null);
            Contract.Ensures(SecondTeamScoring.Passes != null);

            _commandManager = new CommandManager();

            Period = period;
            Number = number;
            Match = match;
            Rank = rank;

            Initialize();
        }

        private void Initialize()
        {
            Contract.Requires(Match != null);

            FirstTeamLineup = new Lineup(this);
            SecondTeamLineup = new Lineup(this);
            FirstTeamScoring = new Scoring(this) { Passes = new HashSet<ScoringPass>() };
            SecondTeamScoring = new Scoring(this) { Passes = new HashSet<ScoringPass>() };

        }

        [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int JamId { get; internal set; }
        public Match Match { get; internal set; }
        public int Period { get; internal set; }
        public int Number { get; internal set; }
        public int Rank { get; internal set; }

        public Lineup FirstTeamLineup { get; internal set; }
        public Lineup SecondTeamLineup { get; internal set; }

        public Scoring FirstTeamScoring { get; internal set; }
        public Scoring SecondTeamScoring { get; internal set; }

        public bool CalledInError { get; internal set; }

        public void FirstTeamScore(int score)
        {
            Contract.Requires(Match != null);
            Contract.Requires(score >= 0 && score <= 6);
            Contract.Requires(FirstTeamScoring != null);
            Contract.Requires(FirstTeamScoring.Passes != null);
            Contract.Requires(FirstTeamLineup != null);
            Contract.Requires(FirstTeamLineup.Jammer != null);

            this.TeamScore(FirstTeamScoring, score);
            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => this.UndoTeamScore(this.FirstTeamScoring),
                    () => this.TeamScore(FirstTeamScoring, score));
            }
        }

        public void SecondTeamScore(int score)
        {
            Contract.Requires(Match != null);
            Contract.Requires(score >= 0 && score <= 6);
            Contract.Requires(SecondTeamScoring != null);
            Contract.Requires(SecondTeamScoring.Passes != null);
            Contract.Requires(SecondTeamLineup != null);
            Contract.Requires(SecondTeamLineup.Jammer != null);

            this.TeamScore(SecondTeamScoring, score);
            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => this.UndoTeamScore(this.SecondTeamScoring),
                    () => this.TeamScore(SecondTeamScoring, score));
            }
        }

        private void TeamScore(Scoring scoring, int score)
        {
            Contract.Requires(Match != null);
            Contract.Requires(scoring != null);
            Contract.Requires(scoring.Passes != null);
            Contract.Requires(score >= 0 && score <= 6);

            int currentPass = scoring.Passes.Count;
            scoring.Passes.Add(new ScoringPass(scoring, currentPass + 1, score));
        }

        private void UndoTeamScore(Scoring scoring)
        {
            Contract.Requires(Match != null);
            Contract.Requires(scoring != null);
            Contract.Requires(scoring.Passes != null);
            Contract.Requires(scoring.Passes.Count > 0);
            var pass = scoring.Passes.Single(sp => sp.Pass == scoring.Passes.Max(sp1 => sp1.Pass));
            scoring.Passes.Remove(pass);
        }

        public int CorrectFirstTeamScore(int pass, int score)
        {
            Contract.Requires(Match != null);
            Contract.Requires(FirstTeamScoring != null);
            Contract.Requires(FirstTeamScoring.Passes != null);
            Contract.Requires(FirstTeamScoring.Passes.Count > 0);
            Contract.Requires(FirstTeamScoring.Passes.Count <= pass);
            Contract.Requires(FirstTeamScoring.Passes.Any(sp => sp.Pass == pass));
            Contract.Requires(score >= 0 && score <= 6);

            Scoring scoring = this.FirstTeamScoring;
            var correctedPass = scoring.Passes.Single(sp => sp.Pass == pass);

            var oldScore = correctedPass.Score;
            CorrectScore(correctedPass, score);

            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => this.CorrectScore(correctedPass, oldScore),
                    () => this.CorrectScore(correctedPass, score));
            }

            return oldScore;
        }

        public int CorrectSecondTeamScore(int pass, int score)
        {
            Contract.Requires(Match != null);
            Contract.Requires(SecondTeamScoring != null);
            Contract.Requires(SecondTeamScoring.Passes != null);
            Contract.Requires(SecondTeamScoring.Passes.Count > 0);
            Contract.Requires(SecondTeamScoring.Passes.Count <= pass);
            Contract.Requires(SecondTeamScoring.Passes.Any(sp => sp.Pass == pass));
            Contract.Requires(score >= 0 && score <= 6);


            Scoring scoring = this.SecondTeamScoring;
            var correctedPass = scoring.Passes.Single(sp => sp.Pass == pass);

            var oldScore = correctedPass.Score;
            CorrectScore(correctedPass, score);

            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => this.CorrectScore(correctedPass, oldScore),
                    () => this.CorrectScore(correctedPass, score));
            }
            return oldScore;
        }

        private void CorrectScore(ScoringPass pass, int newScore)
        {
            Contract.Requires(Match != null);
            Contract.Requires(pass != null);

            pass.Score = newScore;

        }

        public int RemoveFirstTeamScoringPass()
        {
            Contract.Requires(Match != null);
            Contract.Requires(FirstTeamScoring != null);
            Contract.Requires(FirstTeamScoring.Passes != null);
            Contract.Requires(FirstTeamScoring.Passes.Count > 0);


            var oldScore = RemoveTeamScoringPass(FirstTeamScoring);
            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => this.TeamScore(FirstTeamScoring, oldScore),
                    () => this.RemoveTeamScoringPass(FirstTeamScoring));
            }

            return oldScore;
        }

        public int RemoveSecondTeamScoringPass()
        {
            Contract.Requires(Match != null);
            Contract.Requires(SecondTeamScoring != null);
            Contract.Requires(SecondTeamScoring.Passes != null);
            Contract.Requires(SecondTeamScoring.Passes.Count > 0);


            var oldScore = RemoveTeamScoringPass(SecondTeamScoring);
            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => this.TeamScore(SecondTeamScoring, oldScore),
                    () => this.RemoveTeamScoringPass(SecondTeamScoring));
            }

            return oldScore;
        }

        private int RemoveTeamScoringPass(Scoring scoring)
        {
            Contract.Requires(Match != null);

            Contract.Requires(scoring != null);
            Contract.Requires(scoring.Passes != null);
            Contract.Requires(scoring.Passes.Count > 0);

            var pass = scoring.Passes.Single(sp => sp.Pass == scoring.Passes.Max(sp1 => sp1.Pass));
            var oldScore = pass.Score;
            scoring.Passes.Remove(pass);


            return oldScore;
        }

        public void AddFirstTeamJammer(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(Match != null);
            Contract.Requires(Match.FirstTeamRoster != null);
            Contract.Requires(Match.FirstTeamRoster.Any(p => p == player));
            Contract.Requires(FirstTeamLineup != null);
            Contract.Requires(FirstTeamLineup.Jammer == null);
            Contract.Requires(FirstTeamLineup.Pivot != player);
            Contract.Requires(FirstTeamLineup.Blockers != null);
            Contract.Requires(FirstTeamLineup.Blockers.All(p => p != player));
            Contract.Ensures(FirstTeamLineup.Jammer == player);

            FirstTeamLineup.AddJammer(player);
        }
        public void AddFirstTeamPivot(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(Match != null);
            Contract.Requires(Match.FirstTeamRoster != null);
            Contract.Requires(Match.FirstTeamRoster.Any(p => p == player));
            Contract.Requires(FirstTeamLineup != null);
            Contract.Requires(FirstTeamLineup.Jammer != player);
            Contract.Requires(FirstTeamLineup.Pivot == null);
            Contract.Requires(FirstTeamLineup.Blockers != null);
            Contract.Requires(FirstTeamLineup.Blockers.All(p => p != player));
            Contract.Ensures(FirstTeamLineup.Pivot == player);

            FirstTeamLineup.AddPivot(player);
        }
        public void AddFirstTeamBlocker(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(Match != null);
            Contract.Requires(Match.FirstTeamRoster != null);
            Contract.Requires(Match.FirstTeamRoster.Any(p => p == player));
            Contract.Requires(FirstTeamLineup != null);
            Contract.Requires(FirstTeamLineup.Count < 5);
            Contract.Requires(FirstTeamLineup.Jammer != player);
            Contract.Requires(FirstTeamLineup.Pivot != player);
            Contract.Requires(FirstTeamLineup.Blockers != null);
            Contract.Requires(FirstTeamLineup.Blockers.All(p => p != player));
            Contract.Ensures(FirstTeamLineup.Any(p => p == player));

            FirstTeamLineup.AddBlocker(player);
        }

        public void AddSecondTeamJammer(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(Match != null);
            Contract.Requires(Match.SecondTeamRoster != null);
            Contract.Requires(Match.SecondTeamRoster.Any(p => p == player));
            Contract.Requires(SecondTeamLineup != null);
            Contract.Requires(SecondTeamLineup.Jammer == null);
            Contract.Requires(SecondTeamLineup.Pivot != player);
            Contract.Requires(SecondTeamLineup.Blockers != null);
            Contract.Requires(SecondTeamLineup.Blockers.All(p => p != player));
            Contract.Ensures(SecondTeamLineup.Jammer == player);

            SecondTeamLineup.AddJammer(player);
        }
        public void AddSecondTeamPivot(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(Match != null);
            Contract.Requires(Match.SecondTeamRoster != null);
            Contract.Requires(Match.SecondTeamRoster.Any(p => p == player));
            Contract.Requires(SecondTeamLineup != null);
            Contract.Requires(SecondTeamLineup.Jammer != player);
            Contract.Requires(SecondTeamLineup.Pivot == null);
            Contract.Requires(SecondTeamLineup.Blockers != null);
            Contract.Requires(SecondTeamLineup.Blockers.All(p => p != player));
            Contract.Ensures(SecondTeamLineup.Pivot == player);

            SecondTeamLineup.AddPivot(player);
        }
        public void AddSecondTeamBlocker(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(Match != null);
            Contract.Requires(Match.SecondTeamRoster != null);
            Contract.Requires(Match.SecondTeamRoster.Any(p => p == player));
            Contract.Requires(SecondTeamLineup != null);
            Contract.Requires(SecondTeamLineup.Count < 5);
            Contract.Requires(SecondTeamLineup.Jammer != player);
            Contract.Requires(SecondTeamLineup.Pivot != player);
            Contract.Requires(SecondTeamLineup.Blockers != null);
            Contract.Requires(SecondTeamLineup.Blockers.All(p => p != player));
            Contract.Ensures(SecondTeamLineup.Blockers.Any(p => p == player));

            SecondTeamLineup.AddBlocker(player);
        }

        public void CorrectFirstTeamLineup(Player oldPlayer, Player newPlayer)
        {
            Contract.Requires(oldPlayer != null);
            Contract.Requires(newPlayer != null);
            Contract.Requires(Match != null);
            Contract.Requires(Match.FirstTeamRoster != null);
            Contract.Requires(Match.FirstTeamRoster.Any(p => p == newPlayer));
            Contract.Requires(FirstTeamLineup != null);
            Contract.Requires(FirstTeamLineup.Blockers != null);
            Contract.Requires(FirstTeamLineup.Jammer != newPlayer);
            Contract.Requires(FirstTeamLineup.Pivot != newPlayer);
            Contract.Requires(FirstTeamLineup.Blockers.All(p => p != newPlayer));
            Contract.Ensures(FirstTeamLineup.Jammer != oldPlayer);
            Contract.Ensures(FirstTeamLineup.Pivot != oldPlayer);
            Contract.Ensures(FirstTeamLineup.Blockers.All(p => p != oldPlayer));
            Contract.Ensures(
                FirstTeamLineup.Jammer == newPlayer
                || FirstTeamLineup.Pivot == newPlayer
                || FirstTeamLineup.Blockers.Contains(newPlayer));

            FirstTeamLineup.CorrectLineup(oldPlayer, newPlayer);
        }

        public void CorrectSecondTeamLineup(Player oldPlayer, Player newPlayer)
        {
            Contract.Requires(oldPlayer != null);
            Contract.Requires(newPlayer != null);
            Contract.Requires(Match != null);
            Contract.Requires(Match.SecondTeamRoster != null);
            Contract.Requires(Match.SecondTeamRoster.Any(p => p == newPlayer));
            Contract.Requires(SecondTeamLineup != null);
            Contract.Requires(SecondTeamLineup.Blockers != null);
            Contract.Requires(SecondTeamLineup.Jammer != newPlayer);
            Contract.Requires(SecondTeamLineup.Pivot != newPlayer);
            Contract.Requires(SecondTeamLineup.Blockers.All(p => p != newPlayer));
            Contract.Ensures(SecondTeamLineup.Jammer != oldPlayer);
            Contract.Ensures(SecondTeamLineup.Pivot != oldPlayer);
            Contract.Ensures(SecondTeamLineup.Blockers.All(p => p != oldPlayer));
            Contract.Ensures(
                SecondTeamLineup.Jammer == newPlayer
                || SecondTeamLineup.Pivot == newPlayer
                || SecondTeamLineup.Blockers.Contains(newPlayer));

            SecondTeamLineup.CorrectLineup(oldPlayer, newPlayer);
        }

        public void RemoveFromFirstTeamLineup(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(Match != null);
            Contract.Requires(FirstTeamLineup != null);
            Contract.Requires(FirstTeamLineup.Blockers != null);
            Contract.Requires(
                FirstTeamLineup.Jammer == player ||
                FirstTeamLineup.Pivot == player ||
                FirstTeamLineup.Blockers.Any(p => p == player));
            Contract.Ensures(FirstTeamLineup.Jammer != player);
            Contract.Ensures(FirstTeamLineup.Pivot != player);
            Contract.Ensures(FirstTeamLineup.Blockers.All(p => p != player));
            FirstTeamLineup.RemoveFromLineup(player);
        }

        public void RemoveFromSecondTeamLineup(Player player)
        {
            Contract.Requires(player != null);
            Contract.Requires(Match != null);
            Contract.Requires(SecondTeamLineup != null);
            Contract.Requires(SecondTeamLineup.Blockers != null);
            Contract.Requires(
               SecondTeamLineup.Jammer == player ||
               SecondTeamLineup.Pivot == player ||
               SecondTeamLineup.Blockers.Any(p => p == player));
            Contract.Ensures(SecondTeamLineup.Jammer != player);
            Contract.Ensures(SecondTeamLineup.Pivot != player);
            Contract.Ensures(SecondTeamLineup.Blockers.All(p => p != player));

            SecondTeamLineup.RemoveFromLineup(player);
        }

        public void FirstTeamLead()
        {
            Contract.Requires(Match != null);
            Contract.Requires(FirstTeamScoring != null);
            Contract.Ensures(FirstTeamScoring.Lead == true);


            DoTeamLead(FirstTeamScoring);

            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => DoUndoTeamLead(FirstTeamScoring),
                    () => DoTeamLead(FirstTeamScoring));
            }

        }

        public void SecondTeamLead()
        {
            Contract.Requires(Match != null);
            Contract.Requires(SecondTeamScoring != null);
            Contract.Ensures(SecondTeamScoring.Lead == true);


            DoTeamLead(SecondTeamScoring);

            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => DoUndoTeamLead(SecondTeamScoring),
                    () => DoTeamLead(SecondTeamScoring));
            }
        }

        private void DoTeamLead(Scoring scoring)
        {
            Contract.Requires(Match != null);

            Contract.Requires(scoring != null);
            Contract.Requires(!scoring.Lost);
            Contract.Ensures(scoring.Lead);


            scoring.Lead = true;

        }

        private void DoUndoTeamLead(Scoring scoring)
        {
            Contract.Requires(Match != null);

            Contract.Requires(scoring != null);
            Contract.Requires(scoring.Lead);
            Contract.Ensures(!scoring.Lead);

            scoring.Lead = false;

        }

        public void FirstTeamLostLead()
        {
            Contract.Requires(Match != null);
            Contract.Requires(FirstTeamScoring != null);
            Contract.Ensures(FirstTeamScoring.Lost == true);


            DoTeamLostLead(FirstTeamScoring);

            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => DoUndoTeamLostLead(FirstTeamScoring),
                    () => DoTeamLostLead(FirstTeamScoring));
            }
        }

        public void SecondTeamLostLead()
        {
            Contract.Requires(Match != null);
            Contract.Requires(SecondTeamScoring != null);
            Contract.Ensures(SecondTeamScoring.Lost == true);


            DoTeamLostLead(SecondTeamScoring);

            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => DoUndoTeamLostLead(SecondTeamScoring),
                    () => DoTeamLostLead(SecondTeamScoring));
            }
        }


        private void DoTeamLostLead(Scoring scoring)
        {
            Contract.Requires(Match != null);

            Contract.Requires(scoring != null);
            Contract.Requires(!scoring.Lost);
            Contract.Ensures(scoring.Lost);

            scoring.Lost = true;

        }

        private void DoUndoTeamLostLead(Scoring scoring)
        {
            Contract.Requires(Match != null);

            Contract.Requires(scoring != null);
            Contract.Requires(scoring.Lost);
            Contract.Ensures(!scoring.Lost);

            scoring.Lost = false;

        }

        public void FirstTeamCalledJam()
        {
            Contract.Requires(Match != null);
            Contract.Requires(FirstTeamScoring != null);


            DoTeamCalledJam(FirstTeamScoring);

            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => DoUndoTeamCalledJam(FirstTeamScoring),
                    () => DoTeamCalledJam(FirstTeamScoring));
            }
        }

        public void SecondTeamCalledJam()
        {
            Contract.Requires(Match != null);
            Contract.Requires(SecondTeamScoring != null);


            DoTeamCalledJam(SecondTeamScoring);

            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => DoUndoTeamCalledJam(SecondTeamScoring),
                    () => DoTeamCalledJam(SecondTeamScoring));
            }
        }


        private void DoTeamCalledJam(Scoring scoring)
        {
            Contract.Requires(Match != null);

            Contract.Requires(scoring != null);
            Contract.Ensures(scoring.Called);

            if (!scoring.IsLead) CalledInError = true;

            scoring.Called = true;

        }

        private void DoUndoTeamCalledJam(Scoring scoring)
        {
            Contract.Requires(Match != null);

            Contract.Requires(scoring != null);
            Contract.Ensures(!scoring.Called);

            CalledInError = false;

            scoring.Called = false;

        }

        public void FirstTeamStarPassed()
        {
            Contract.Requires(Match != null);
            Contract.Requires(FirstTeamLineup != null);
            Contract.Requires(FirstTeamScoring != null);
            Contract.Requires(FirstTeamScoring.Passes != null);
            Contract.Requires(FirstTeamLineup.Pivot != null);
            Contract.Requires(FirstTeamLineup.Jammer != null);


            DoTeamStarPassed(FirstTeamLineup, FirstTeamScoring);
            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => this.UndoTeamStarPassed(FirstTeamLineup, FirstTeamScoring),
                    () => this.DoTeamStarPassed(FirstTeamLineup, FirstTeamScoring));
            }
        }

        public void SecondTeamStarPassed()
        {
            Contract.Requires(Match != null);
            Contract.Requires(SecondTeamLineup != null);
            Contract.Requires(SecondTeamScoring != null);
            Contract.Requires(SecondTeamScoring.Passes != null);
            Contract.Requires(SecondTeamLineup.Pivot != null);
            Contract.Requires(SecondTeamLineup.Jammer != null);


            DoTeamStarPassed(SecondTeamLineup, SecondTeamScoring);
            if (_commandManager != null)
            {
                _commandManager.Add(
                    () => this.UndoTeamStarPassed(SecondTeamLineup, SecondTeamScoring),
                    () => this.DoTeamStarPassed(SecondTeamLineup, SecondTeamScoring));
            }
        }

        private void DoTeamStarPassed(Lineup lineup, Scoring scoring)
        {
            Contract.Requires(Match != null);

            Contract.Requires(lineup != null);
            Contract.Requires(scoring != null);
            Contract.Requires(scoring.Passes != null);
            Contract.Requires(lineup.Pivot != null);
            Contract.Requires(lineup.Jammer != null);

            lineup.StarPassed = true;
            scoring.StarPassed = true;


        }

        private void UndoTeamStarPassed(Lineup lineup, Scoring scoring)
        {
            Contract.Requires(Match != null);

            Contract.Requires(lineup != null);
            Contract.Requires(scoring != null);

            lineup.StarPassed = false;
            scoring.StarPassed = false;


        }

        public bool VerifyJam()
        {
            Contract.Requires(FirstTeamLineup != null);
            Contract.Requires(SecondTeamLineup != null);
            Contract.Requires(FirstTeamScoring != null);
            Contract.Requires(SecondTeamScoring != null);

            if (!FirstTeamLineup.VerifyLineup() || !SecondTeamLineup.VerifyLineup()) return false;
            if (!FirstTeamScoring.VerifyScoring() || !SecondTeamScoring.VerifyScoring()) return false;

            return true;
        }
    }
}
